package com.moxie.ocr.ocr.utils;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RenderScript;
import android.renderscript.ScriptIntrinsicYuvToRGB;
import android.renderscript.Type;
import android.util.TimingLogger;
import android.view.Surface;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.ref.SoftReference;
import java.util.Locale;


public class Util {

    public static final String PUBLIC_LOG_TAG = "com.linkface.card";

    public static final String TIMING_LOG_TAG = "STTiming";

    private static final String TAG = Util.class.getSimpleName();

    public static void setupTextPaintStyle(Paint paint) {
        paint.setColor(Color.WHITE);
        paint.setStyle(Paint.Style.FILL);
        paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
        paint.setAntiAlias(true);
        float[] black = {0f, 0f, 0f};
        paint.setShadowLayer(1.5f, 0.5f, 0f, Color.HSVToColor(200, black));
    }

    private static RenderScript mRS = null;

    static ScriptIntrinsicYuvToRGB mYuvToRgb = null;

    static Allocation ain = null;

    static Allocation aOut = null;

    static Bitmap bitmap = null;

    static Bitmap sCropBitmap = null;

    static byte[] bitmapByte;

    @SuppressLint("NewApi")
    public static Bitmap NV21ToRGBABitmap(byte[] nv21, int width, int height,
                                          Context context) {

//        recycleBitmap(bitmap);

        TimingLogger timings = new TimingLogger(TIMING_LOG_TAG, "NV21ToRGBABitmap");

        Rect rect = new Rect(0, 0, width, height);
        MXLog.i(TAG, "NV21ToRGBABitmap", "nv21.length", nv21.length, "width", width, "height", height);
        YuvImage yuvImage = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
        timings.addSplit("NV21 bytes to YuvImage");

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        yuvImage.compressToJpeg(rect, 90, baos);
        bitmapByte = baos.toByteArray();
        timings.addSplit("YuvImage crop and compress to Jpeg Bytes");
        // already cropped
        rect.right = rect.right - rect.left;
        rect.left = 0;
        rect.bottom = rect.bottom - rect.top;
        rect.top = 0;

        bitmap = BitmapFactory.decodeByteArray(bitmapByte, 0, bitmapByte.length);
        timings.addSplit("Jpeg Bytes to Bitmap");
        timings.dumpToLog();
        return bitmap;
    }

    @SuppressLint("NewApi")
    public static Bitmap NV21ToRGBABitmap(byte[] nv21, int width, int height, int rotateDegree, Rect cropRect,
                                          Context context) {

        recycleBitmap(bitmap);

        TimingLogger timings = new TimingLogger(TIMING_LOG_TAG, "NV21ToRGBABitmap");

        Rect rect = new Rect(0, 0, width, height);
        if (cropRect != null) {
            if (rotateDegree == 0 || rotateDegree == 180) {
                rect = new Rect(cropRect.left, cropRect.top, cropRect.right, cropRect.bottom);
            } else if (rotateDegree == 90 || rotateDegree == 270) {
                rect = new Rect(cropRect.top, cropRect.left, cropRect.bottom, cropRect.right);
            }
        }

        if (!isCPUInfo64()) {

            try {
                Class.forName("android.renderscript.Element$DataKind").getField("PIXEL_YUV");
                Class.forName("android.renderscript.ScriptIntrinsicYuvToRGB");
                byte[] imageData = nv21;
                if (mRS == null) {
                    mRS = RenderScript.create(context);
                    mYuvToRgb = ScriptIntrinsicYuvToRGB.create(mRS, Element.U8_4(mRS));
                    Type.Builder tb = new Type.Builder(mRS,
                            Element.createPixel(mRS, Element.DataType.UNSIGNED_8, Element.DataKind.PIXEL_YUV));
                    tb.setX(width);
                    tb.setY(height);
                    tb.setMipmaps(false);
                    tb.setYuvFormat(ImageFormat.NV21);
                    ain = Allocation.createTyped(mRS, tb.create(), Allocation.USAGE_SCRIPT);
                    timings.addSplit("Prepare for ain");
                    Type.Builder tb2 = new Type.Builder(mRS, Element.RGBA_8888(mRS));
                    tb2.setX(width);
                    tb2.setY(height);
                    tb2.setMipmaps(false);
                    aOut = Allocation.createTyped(mRS, tb2.create(), Allocation.USAGE_SCRIPT & Allocation.USAGE_SHARED);
                    timings.addSplit("Prepare for aOut");
                    bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
                    timings.addSplit("Create Bitmap");
                }
                ain.copyFrom(imageData);
                timings.addSplit("ain copyFrom");
                mYuvToRgb.setInput(ain);
                timings.addSplit("setInput ain");
                mYuvToRgb.forEach(aOut);
                timings.addSplit("NV21 to ARGB forEach");
                aOut.copyTo(bitmap);
                timings.addSplit("Allocation to Bitmap");
            } catch (Exception e) {
                YuvImage yuvImage = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
                timings.addSplit("NV21 bytes to YuvImage");

                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                yuvImage.compressToJpeg(rect, 90, baos);
                bitmapByte = baos.toByteArray();
                timings.addSplit("YuvImage crop and compress to Jpeg Bytes");
                // already cropped
                rect.right = rect.right - rect.left;
                rect.left = 0;
                rect.bottom = rect.bottom - rect.top;
                rect.top = 0;

                bitmap = BitmapFactory.decodeByteArray(bitmapByte, 0, bitmapByte.length);
                timings.addSplit("Jpeg Bytes to Bitmap");
            }
        } else {
            MXLog.i(TAG, "NV21ToRGBABitmap", "nv21.length", nv21.length, "width", width, "height", height);
            YuvImage yuvImage = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
            timings.addSplit("NV21 bytes to YuvImage");

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            yuvImage.compressToJpeg(rect, 90, baos);
            bitmapByte = baos.toByteArray();
            timings.addSplit("YuvImage crop and compress to Jpeg Bytes");
            // already cropped
            rect.right = rect.right - rect.left;
            rect.left = 0;
            rect.bottom = rect.bottom - rect.top;
            rect.top = 0;

            bitmap = BitmapFactory.decodeByteArray(bitmapByte, 0, bitmapByte.length);
            timings.addSplit("Jpeg Bytes to Bitmap");
        }

        if (rotateDegree >= 0 && bitmap != null) {
            Matrix matrix = new Matrix();
            matrix.postRotate(rotateDegree);
            bitmap = Bitmap.createBitmap(bitmap, rect.left, rect.top,
                    Math.min(rect.width(), bitmap.getWidth() - rect.left),
                    Math.min(rect.height(), bitmap.getHeight() - rect.top), matrix, false);
            timings.addSplit("Bitmap rotate & crop");
        }

        timings.dumpToLog();
        return bitmap;
    }

    public static Bitmap byteToBitmap(byte[] imgByte) {
        InputStream input = null;
        Bitmap bitmap = null;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = 1;
        input = new ByteArrayInputStream(imgByte);
        SoftReference<Bitmap> softRef = new SoftReference<Bitmap>(BitmapFactory.decodeStream(
                input, null, options));
        bitmap = (Bitmap) softRef.get();
        if (imgByte != null) {
            imgByte = null;
        }

        try {
            if (input != null) {
                input.close();
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return bitmap;
    }

    private static final String PROC_CPU_INFO_PATH = "/proc/cpuinfo";

    /**
     * Read the first line of "/proc/cpuinfo" file, and check if it is 64 bit.
     */
    private static boolean isCPUInfo64() {
        File cpuInfo = new File(PROC_CPU_INFO_PATH);
        if (cpuInfo != null && cpuInfo.exists()) {
            InputStream inputStream = null;
            BufferedReader bufferedReader = null;
            try {
                inputStream = new FileInputStream(cpuInfo);
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream), 512);
                String line = bufferedReader.readLine();
                if (line != null && line.length() > 0 && line.toLowerCase(Locale.US).contains("arch64")) {
                    return true;
                }
            } catch (Throwable t) {
            } finally {
                try {
                    if (bufferedReader != null) {
                        bufferedReader.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }

                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    public static void setCameraDisplayOrientation(int rotation, int cameraId, Camera camera) {
        CameraInfo info = new CameraInfo();
        Camera.getCameraInfo(cameraId, info);
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }
        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;  // compensate the mirror
        } else {  // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }
        camera.setDisplayOrientation(result);
    }

    public static void recycleBitmap(Bitmap bitmap) {
        if (bitmap != null) {
            bitmap.recycle();
            bitmap = null;
        }
    }

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
     */
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

    /**
     * 按8：5扩展裁剪框为较小空白边界的80%
     * 横向：扩展横向单边空隙的80%
     * 纵向：扩展纵向单边空隙的80%
     * */
    public static Rect extendRect(Rect cropRect, int screenWidth, int screenHeight, int rotateDegree, boolean isVertical) {
        Rect extendedRect = new Rect();
        int diff = 0;
        int newWidth = 0;
        int newHeight = 0;
        int left = 0;
        int right = 0;
        int top = 0;
        int bottom = 0;
        float scale = 0.8f;

        if (cropRect == null)
            return extendedRect;

        // 按cropRect的高度来扩(即屏幕宽度）
        if(rotateDegree==90 || rotateDegree==270) {
            // 竖向横卡  竖向竖卡 cropRect(top, left, bottom, right) = (54, 656, 1026, 1262)
            diff = (int)(cropRect.left * scale);
            diff = Math.min(diff, 70);
            top = cropRect.left - diff;
            if(top < 0) {
                top = 0;
            }
            bottom = cropRect.right + diff;
            if(bottom > screenHeight) {
                bottom = screenHeight;
            }
            newHeight = bottom - top;

            if(isVertical) {
                // 竖向竖卡
                newWidth = (int) (newHeight * 1.6f);
            } else {
                // 竖向横卡
                newWidth = (int) (newHeight / 1.6f);
            }

            left = (cropRect.top + cropRect.bottom) / 2 - newWidth / 2;
            if(left < 0) {
                left = 0;
            }
            right = left + newWidth;
            if(right > screenWidth) {
                right = screenWidth;
            }

            left &= ~1;
            right &= ~1;
            top &= ~1;
            bottom &= ~1;
            extendedRect.set(top, left, bottom, right);

        } else {
            // rotateDegree=0, 180 cropRect(top, left, bottom, right) 横向横卡或横向竖卡
            diff = (int) (cropRect.top * scale);
            diff = Math.min(diff, 70);

            left = cropRect.left - diff;
            if(left < 0) {
                left = 0;
            }
            right = cropRect.right + diff;
            if(right > screenWidth) {
                right = screenWidth;
            }
            newWidth = right - left;

            if(!isVertical) {
                // 横向横卡
                newHeight = (int)(newWidth / 1.6f);
            } else {
                // 横向竖卡
                newHeight = (int)(newWidth * 1.6f);
            }

            top = (cropRect.top + cropRect.bottom) / 2 - newHeight / 2;
            if(top < 0) {
                top = 0;
            }
            bottom = top + newHeight;
            if(bottom > screenHeight) {
                bottom = screenHeight;
            }

            // 确保矩形的上下左右均为偶数（模型只识别边框为偶数的矩形）
            left &= ~1;
            right &= ~1;
            top &= ~1;
            bottom &= ~1;

            extendedRect.set(left, top, right, bottom);
        }

        return extendedRect;
    }

}
